import React, { useState, useEffect, useRef, useImperativeHandle, forwardRef } from 'react'
import {
  Color,
  Group,
  WebGLRenderer,
  OrthographicCamera,
  Vector3,
  Scene,
  MOUSE,
  CircleGeometry,
  MeshBasicMaterial,
  Mesh,
  LineDashedMaterial,
  LineBasicMaterial,
  BufferGeometry,
  Line,
  Shape,
  ShapeGeometry,
  DoubleSide,
  TextGeometry,
  Vector2,
  EllipseCurve,
  Box3,
  Raycaster,
  BoxBufferGeometry
} from 'three'
import { TTFLoader } from 'three/examples/jsm/loaders/TTFLoader.js'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { SelectionBox } from 'three/examples/jsm/interactive/SelectionBox.js'
import { Font } from 'three/examples/jsm/loaders/FontLoader.js'
import { SelectionHelper } from 'three/examples/jsm/interactive/SelectionHelper.js'
import { getHexColor, getTypeOfVariable, randomColor } from '@/utils/tools'
import { Loading } from '@alifd/next'
import RightClickMenu from '../Overlay/index'
import { useMemoizedFn, useUpdateEffect, useMount, useEventListener, useTitle } from 'ahooks'
import store from '@/store'
import { isUndefined, isEmpty } from 'lodash-es'
import styles from './index.module.scss'

const selectedColor = new Color('#1890ff')
const divideColor = new Color('#ff4500')
const entryColor = new Color('#00ffff')
const divideColor2 = new Color('#32CD32')
const scanningColor = new Color('#00ff7f')

SelectionHelper.prototype.onSelectStart = function (event) {
  if (event.button === 0) {
    this.renderer.domElement.parentElement.appendChild(this.element)

    this.element.style.left = event.clientX + 'px'
    this.element.style.top = event.clientY + 'px'
    this.element.style.width = '0px'
    this.element.style.height = '0px'

    this.startPoint.x = event.clientX
    this.startPoint.y = event.clientY
  }
}

SelectionHelper.prototype.onSelectMove = function (event) {
  this.pointBottomRight.x = Math.max(this.startPoint.x, event.clientX)
  this.pointBottomRight.y = Math.max(this.startPoint.y, event.clientY)
  this.pointTopLeft.x = Math.min(this.startPoint.x, event.clientX)
  this.pointTopLeft.y = Math.min(this.startPoint.y, event.clientY)

  this.element.style.left = this.pointTopLeft.x + 'px'
  this.element.style.top = this.pointTopLeft.y + 'px'
  this.element.style.width = this.pointBottomRight.x - this.pointTopLeft.x + 'px'
  this.element.style.height = this.pointBottomRight.y - this.pointTopLeft.y + 'px'
}

SelectionHelper.prototype.onSelectOver = function () {
  if (this.element.parentElement) this.element.parentElement.removeChild(this.element)
}

const zoom = 1

const Graph = (
  {
    setCoord,
    autoplan,
    updateBIM,
    updateApartmentName,
    manualplan,
    deleteBIMs,
    setButtonLoading,
    removeDb,
    setDisplayState,
    splitScreenState,
    box,
    back,
    forward,
    scanstationCheck
  },
  ref
) => {
  const [graphState, graphDispatchers] = store.useModel('graph')
  const {
    data,
    bim: _bim,
    selectedIds,
    font,
    ricentDrawing,
    ricentDrawingBackup,
    stage,
    idToEye,
    onlyToEye,
    ugmId
  } = graphState

  const setSelectedIds = useMemoizedFn(cells => graphDispatchers.update({ selectedIds: Object.keys(cells) }))

  const [bim, setBIM] = useState()
  const [selectedCells, setSelectedCells] = useState({})
  const [selectedRicentCells, setSelectedRicentCells] = useState({})
  const [roomIds, setRoomIds] = useState([])
  const [scanstationIds, setScanstationIds] = useState([])
  const [doorIds, setDoorIds] = useState([])
  const [windowIds, setWindowIds] = useState([])
  const [boundingboxIds, setBoundingboxIds] = useState([])
  const [dataForRender, setDataForRender] = useState({})
  const [selectionBox, setSelectionBox] = useState()
  const [helper, setHelper] = useState({})
  const [rotateZ, setRotateZ] = useState(0.0001)
  const [loadingVisible, setLoadingVisible] = useState(false)

  const [cellVisible, setCellVisible] = useState({})
  const [roomVisible, setRoomVisible] = useState(true)
  const [scanstationVisible, setScanstationVisible] = useState(true)
  const [doorVisible, setDoorVisible] = useState(true)
  const [windowVisible, setWindowVisible] = useState(true)
  const [ricentDrawingVisible, setRicentDrawingVisible] = useState(false)
  const [ricentDrawingBackupVisible, setRicentDrawingBackupVisible] = useState(false)
  const [planningScanstationState, setPlanningScanstationState] = useState(false)
  const [newPlanningScanstationState, setNewPlanningScanstationState] = useState(false)

  const graphRef = useRef()
  const sceneRef = useRef()
  const cameraRef = useRef()
  const rendererRef = useRef()
  const raycasterRef = useRef()
  const widthRef = useRef()
  const heightRef = useRef()
  const controlsRef = useRef()
  const selectedCellsRef = useRef({})
  const groupRef = useRef(new Group())
  const pointermoveRef = useRef(false)
  const SRef = useRef()
  const centerRef = useRef()
  const groupPositionRef = useRef()
  const cellVisibleRef = useRef({})
  const idsForVisible = useRef([])
  const isVisibleRef = useRef()
  const buttonRef = useRef(false)
  const animationIdRef = useRef()
  const pointsToPlanRef = useRef([])
  const textGeosRef = useRef({})
  const lineMatsRef = useRef({ dash: {}, notDash: {} })
  const apartmentColorRef = useRef({})
  const rightClickMenuRef = useRef({})
  const selectionBoxStartPRef = useRef([0, 0])

  useTitle(ugmId)

  useMount(() => {
    if (!font) {
      new TTFLoader().load('font/chinese.msyh.ttf', data => {
        graphDispatchers.update({ font: new Font(data) })
      })
    }
  })

  useEffect(() => {
    init()
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  useEffect(() => {
    const selectedCells = {}
    groupRef.current.children.forEach(i => {
      if (selectedIds.includes(i.bimId)) {
        selectedCells[i.bimId] = i
      }
    })
    setSelectedCells(selectedCells)
  }, [selectedIds])

  useEffect(() => {
    if (animationIdRef.current) window.cancelAnimationFrame(animationIdRef.current)
    const selectedScanstations = []
    let scanningWallIds = []
    groupRef.current.children.forEach(cell => {
      if (selectedCells[cell.bimId]) {
        if (bim.docs[cell.bimId]) {
          const definition = bim.docs[cell.bimId].definition
          switch (definition) {
            case 'scanstation':
              selectedScanstations.push(bim.docs[cell.bimId])
              break
            default:
          }
        }
        selected(cell)
      } else if (selectedRicentCells[cell.bimId]) {
        selected(cell)
      } else {
        unSelected(cell)
      }
    })
    selectedScanstations.forEach(cell => {
      const lineIds = [...(cell.drywalls || []), ...(cell.mainwalls || [])]
      scanningWallIds = [...scanningWallIds, ...lineIds]
    })
    if (scanningWallIds.length > 0) {
      groupRef.current.children.forEach(cell => {
        if (scanningWallIds.includes(cell.bimId)) {
          scanning(cell)
          cell.renderOrder = 3
          cell.position.z = 102
        } else {
          cell.renderOrder = 1
          cell.position.z = 0
        }
      })
    }
    changesScanstationOrder()
    // setSelectedIds(Object.keys(selectedCells))
    selectedCellsRef.current = selectedCells
    animate()
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [selectedCells, selectedRicentCells, data, bim])

  useEffect(() => {
    if (bim && !isEmpty(bim)) {
      const ids = selectedIds
      const BIM_IDs = ids.reduce((p, c) => [...p, ...(bim.docs[c] && bim.docs[c].data ? bim.docs[c].data : [])], [])
      const newIds = groupRef.current.children
        .filter(i => ids.includes(i.bimId) || BIM_IDs.includes(i.bimId))
        .map(i => i.bimId)
      const oldIds = Object.keys(selectedCellsRef.current)
      const sign = oldIds.every(i => newIds.includes(i)) && newIds.every(i => oldIds.includes(i))
      if (!sign) {
        const newSelectedCells = {}
        groupRef.current.children.forEach(i => {
          if (newIds.includes(i.bimId)) newSelectedCells[i.bimId] = i
        })
        setSelectedCells(shouldSelectedObj(newSelectedCells))
      }
    }
    // eslint-disable-next-line no-use-before-define
  }, [selectedIds, bim, shouldSelectedObj, setSelectedCells])

  useEffect(() => {
    pointsToPlanRef.current.forEach(p => groupRef.current.remove(p))
    pointsToPlanRef.current = []
  }, [stage, planningScanstationState, newPlanningScanstationState])

  useEffect(() => {
    for (const key in dataForRender) {
      switch (key) {
        case 'room':
          dataForRender.room.forEach(i => {
            cellVisibleRef.current[i.id] = !ricentDrawingBackupVisible && !ricentDrawingVisible && roomVisible
          })
          break
        case 'scanstation':
          dataForRender.scanstation.forEach(i => {
            cellVisibleRef.current[i.id] = !ricentDrawingBackupVisible && !ricentDrawingVisible && scanstationVisible
          })
          break
        case 'door':
          dataForRender.door.forEach(i => {
            cellVisibleRef.current[i.id] = !ricentDrawingBackupVisible && !ricentDrawingVisible && doorVisible
          })
          break
        case 'window':
          dataForRender.window.forEach(i => {
            cellVisibleRef.current[i.id] = !ricentDrawingBackupVisible && !ricentDrawingVisible && windowVisible
          })
          break
        default:
          if (getTypeOfVariable(dataForRender[key]) === 'Array') {
            dataForRender[key].forEach(i => {
              cellVisibleRef.current[i.id] = !ricentDrawingBackupVisible && !ricentDrawingVisible
            })
          }
      }
    }
    setCellVisible({ ...cellVisibleRef.current })
  }, [
    dataForRender,
    roomVisible,
    scanstationVisible,
    doorVisible,
    windowVisible,
    ricentDrawingBackupVisible,
    ricentDrawingVisible
  ])

  useEffect(() => {
    groupRef.current.children.forEach(cell => {
      if ((data.docs[cell.bimId] && data.docs[cell.bimId].definition === 'boundingbox') || cell.isRicentDrawingRoom) {
        if (cell.backup) {
          cell.material.visible = ricentDrawingBackupVisible
        } else {
          cell.material.visible = ricentDrawingVisible
        }
      } else {
        if (cellVisible[cell.bimId] === false) {
          cell.material.visible = false
        } else {
          cell.material.visible = true
        }
      }
    })
    cellVisibleRef.current = { ...cellVisible }
  }, [cellVisible, data, ricentDrawingBackupVisible, ricentDrawingVisible])

  useEffect(() => {
    changeCellsDisplay({ ids: idToEye.ids, isVisible: false })
    // eslint-disable-next-line no-use-before-define
  }, [idToEye, changeCellsDisplay])

  useEffect(() => {
    changeCellsDisplay({ ids: onlyToEye.ids, isVisible: true })
    // eslint-disable-next-line no-use-before-define
  }, [onlyToEye, changeCellsDisplay])

  useEffect(() => {
    if (!isEmpty(data)) setBoundingboxIds(data.cols.boundingbox.map(i => i.id))
  }, [data])

  useEffect(() => {
    if (!isEmpty(_bim)) {
      const { cols, docs } = _bim
      const newBim = { cols: {}, docs: {} }
      if (stage.includes('concrete')) {
        for (const id in docs) {
          const i = docs[id]
          if (i.definition === 'scanstation' && !i.room) {
            newBim.docs[i.id] = i
          }
        }
      } else {
        for (const id in docs) {
          const i = docs[id]
          if (i.definition !== 'scanstation' || (i.definition === 'scanstation' && i.room)) {
            newBim.docs[i.id] = i
          }
        }
      }
      for (const key in cols) {
        newBim.cols[key] = []
      }
      for (const key in newBim.docs) {
        const node = newBim.docs[key]
        const definition = node.definition
        if (definition) {
          newBim.cols[definition].push(node)
        }
      }
      setBIM(newBim)
      setRoomIds(newBim.cols.room.map(i => i.id))
      setScanstationIds(newBim.cols.scanstation.map(i => i.id))
      setDoorIds(newBim.cols.door.map(i => i.id))
      setWindowIds(newBim.cols.window.map(i => i.id))
    }
  }, [_bim, stage])

  useEffect(() => {
    if (!isEmpty(dataForRender) && (!isEmpty(ricentDrawing) || !isEmpty(ricentDrawingBackup))) {
      setDisplayState(true)
      setLoadingVisible(true)
      load()
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [ricentDrawing, ricentDrawingBackup])

  useEffect(() => {
    const group = groupRef.current
    group.rotation.z = rotateZ
    fitAndCenter()
    // eslint-disable-next-line no-use-before-define
  }, [rotateZ, fitAndCenter])

  useUpdateEffect(() => {
    if (data && bim) {
      try {
        handleData()
      } catch (err) {
        console.error(err)
      }
    }
    //eslint-disable-next-line react-hooks/exhaustive-deps
  }, [data, bim])

  useUpdateEffect(() => {
    if (data && bim) {
      try {
        handleData()
      } catch (err) {
        console.error(err)
      }
    }
    //eslint-disable-next-line react-hooks/exhaustive-deps
  }, [data, bim])

  useUpdateEffect(() => {
    if (!isEmpty(dataForRender)) {
      setLoadingVisible(true)
      load()
    }
  }, [dataForRender, font])

  useUpdateEffect(() => {
    if (fitAndCenter) fitAndCenter()
    // eslint-disable-next-line no-use-before-define
  }, [splitScreenState, fitAndCenter])

  useUpdateEffect(() => {
    if (fitAndCenter) fitAndCenter()
    // eslint-disable-next-line no-use-before-define
  }, [box, fitAndCenter])

  const changesScanstationOrder = useMemoizedFn(() => {
    groupRef.current.children.forEach(cell => {
      if (cell.bimId && bim.docs[cell.bimId] && bim.docs[cell.bimId].definition === 'scanstation') {
        cell.position.z = 101
        cell.renderOrder = 3
        if (cell.selected) {
          cell.position.z = 102
          cell.renderOrder = 4
        }
      }
      if (getTypeOfVariable(cell.backup) === 'Boolean') {
        if (cell.isRicentDrawingRoom) {
          cell.position.z = 105
          cell.renderOrder = 3
          if (cell.selected) {
            cell.position.z = 106
            cell.renderOrder = 4
          }
        } else {
          cell.position.z = 100
          cell.renderOrder = 1
        }
      }
    })
  })

  const getVisible = useMemoizedFn((cell, ids, isVisible) => {
    if (isUndefined(ids)) ids = idsForVisible.current
    idsForVisible.current = ids
    if (isUndefined(isVisible)) isVisible = isVisibleRef.current
    isVisibleRef.current = isVisible
    if (roomIds.includes(cell.bimId)) {
      return roomVisible
    } else if (scanstationIds.includes(cell.bimId)) {
      return scanstationVisible
    } else if (doorIds.includes(cell.bimId)) {
      return doorVisible
    } else if (windowIds.includes(cell.bimId)) {
      return windowVisible
    } else if (boundingboxIds.includes(cell.bimId) || cell.isRicentDrawingRoom) {
      if (cell.backup) {
        return ricentDrawingBackupVisible
      } else {
        return ricentDrawingVisible
      }
    } else {
      if (!isVisible) {
        return !ids.includes(cell.bimId)
      } else {
        if (ids.length > 0) {
          if (ids.includes(cell.bimId)) {
            return true
          } else if (cell.bimId) {
            return false
          }
        } else {
          return true
        }
      }
    }
  })

  const changeCellsDisplay = useMemoizedFn(({ ids, isVisible }) => {
    const obj = {}
    groupRef.current.children.forEach(cell => {
      obj[cell.bimId] = getVisible(cell, ids, isVisible)
    })
    setCellVisible({ ...cellVisible, ...obj })
  })

  const load = useMemoizedFn(() => {
    console.time('loadData时间')
    loadData()
    console.timeEnd('loadData时间')
    console.time('fitAndCenter时间')
    fitAndCenter()
    console.timeEnd('fitAndCenter时间')
    setSelectedIds(shouldSelectedObj(selectedCellsRef.current))
    setLoadingVisible(false)
  })

  const init = useMemoizedFn(async () => {
    initRender()
    initCamera()
    initScene()
    initControls()
    animate()
    rendererRef.current.render(sceneRef.current, cameraRef.current)
    createRaycaster()
    setSelectionBox(new SelectionBox(cameraRef.current, sceneRef.current))
    setHelper(new SelectionHelper(selectionBox, rendererRef.current, 'selectBox'))
  })

  // 监听事件
  // 窗口变动触发的函数
  const onWindowResize = useMemoizedFn(() => {
    widthRef.current = graphRef.current.clientWidth
    heightRef.current = graphRef.current.clientHeight
    cameraRef.current.left = -widthRef.current
    cameraRef.current.right = widthRef.current
    cameraRef.current.top = heightRef.current
    cameraRef.current.bottom = -heightRef.current
    cameraRef.current.aspect = widthRef.current / heightRef.current
    cameraRef.current.updateProjectionMatrix()
    rendererRef.current.setSize(widthRef.current, heightRef.current)
    rendererRef.current.render(sceneRef.current, cameraRef.current)
  })

  const initRender = useMemoizedFn(() => {
    widthRef.current = graphRef.current.clientWidth
    heightRef.current = graphRef.current.clientHeight
    rendererRef.current = new WebGLRenderer({ antialias: true, alpha: true })
    rendererRef.current.setClearColor(0x212830, 1.0)
    rendererRef.current.setPixelRatio(window.devicePixelRatio)
    rendererRef.current.setSize(widthRef.current, heightRef.current)
    graphRef.current.appendChild(rendererRef.current.domElement)
  })

  const initCamera = useMemoizedFn(() => {
    cameraRef.current = new OrthographicCamera(
      -widthRef.current,
      widthRef.current,
      heightRef.current,
      -heightRef.current,
      0,
      100000
    )
    cameraRef.current.position.z = 1000
    cameraRef.current.lookAt(new Vector3(0, 0, 0))
    cameraRef.current.updateProjectionMatrix()
  })

  const initScene = useMemoizedFn(() => {
    sceneRef.current = new Scene()
  })

  const initControls = useMemoizedFn(() => {
    controlsRef.current = new OrbitControls(cameraRef.current, rendererRef.current.domElement)
    // 禁用左键旋转
    controlsRef.current.enableRotate = false
    controlsRef.current.mouseButtons = {
      MIDDLE: MOUSE.PAN
    }
  })

  const animate = useMemoizedFn(() => {
    animationIdRef.current = requestAnimationFrame(animate)
    controlsRef.current.update()
    rendererRef.current.render(sceneRef.current, cameraRef.current)
  })

  const handleData = useMemoizedFn(() => {
    const cols = { ...data.cols, ...bim.cols }
    const newDataForRender = {}
    apartmentColorRef.current = {}
    if (cols.point && cols.point.length > 0) {
      for (const key in cols) {
        const arr = cols[key]
        switch (key) {
          case 'line':
          case 'polyline':
          case 'solid':
          case 'arc':
          case 'ellipse':
          case 'circle':
          case 'text':
          case 'scanstation':
          case 'separator':
          case 'spatiallivingroom':
          case 'door':
          case 'window':
            if (!newDataForRender[key]) newDataForRender[key] = []
            arr.forEach(i => newDataForRender[key].push(i))
            break
          case 'layer':
            arr.forEach(i => {
              if (i.type === 'Beam') newDataForRender['layerBeamId'] = i.id
              if (i.type === 'Column') newDataForRender['layerColumnId'] = i.id
              if (i.type === 'MainWall') newDataForRender['layerMainWallId'] = i.id
            })
            break
          case 'room':
            if (!newDataForRender[key]) newDataForRender[key] = []
            arr.forEach(i => {
              if (i.points) newDataForRender[key].push(i)
            })
            break
          case 'boundingbox':
            if (!newDataForRender[key]) newDataForRender[key] = []
            arr.forEach(i => newDataForRender[key].push(i))
            break
          case 'point':
            break
          case 'rendermaterial':
            break
          case 'apartment':
            arr.forEach(i => {
              apartmentColorRef.current[i.id] = new Color(randomColor())
            })
            break
          default:
        }
      }
    }

    setDataForRender(newDataForRender)
  })

  const loadData = useMemoizedFn(() => {
    groupRef.current.clear()
    for (const key in dataForRender) {
      const arr = dataForRender[key]
      switch (key) {
        case 'line':
          arr.forEach(i => groupRef.current.add(createLine(i)))
          break
        case 'point':
          break
        case 'rendermaterial':
          break
        case 'layer':
          break
        case 'polyline':
          break
        case 'solid':
          break
        case 'arc':
          arr.forEach(i => groupRef.current.add(createArc(i)))
          break
        case 'ellipse':
          arr.forEach(i => groupRef.current.add(createEllipse(i)))
          break
        case 'circle':
          break
        case 'text':
          if (font) arr.forEach(i => groupRef.current.add(createText(i)))
          break
        case 'room':
          arr.forEach(i => groupRef.current.add(createRoom(i)))
          break
        case 'scanstation':
          arr.forEach(i => groupRef.current.add(createScanstation(i)))
          break
        case 'separator':
          arr.forEach(i => groupRef.current.add(createSeparator(i)))
          break
        case 'spatiallivingroom':
          arr.forEach(i => createSpatiallivingroom(i).forEach(j => groupRef.current.add(j)))
          break
        case 'door':
          arr.forEach(i => groupRef.current.add(createDoor(i)))
          break
        case 'window':
          arr.forEach(i => groupRef.current.add(createWindow(i)))
          break
        case 'boundingbox':
          if (!isEmpty(ricentDrawing) || !isEmpty(ricentDrawingBackup)) {
            const structure = arr.find(o => o.type === 'structure')
            const architecture = arr.find(o => o.type === 'architecture')
            const meshes = createBoundingbox(stage.includes('concrete') ? structure : architecture)
            meshes.forEach(o => groupRef.current.add(o))
          }
          break
        default:
      }
    }
    groupRef.current.children.forEach(i => {
      i.material.visible = getVisible(i)
    })
    SRef.current = null
    centerRef.current = null
    groupRef.current.scale.set(1, 1, 1)
    groupRef.current.position.x = 0
    groupRef.current.position.y = 0
    groupRef.current.position.z = 0
    cameraRef.current.position.x = 0
    cameraRef.current.position.y = 0
    changesScanstationOrder()
    sceneRef.current.clear()
    sceneRef.current.add(groupRef.current)
    controlsRef.current.reset()
  })

  const createScanstation = useMemoizedFn(({ point, id, isNewScan, color }) => {
    const geometry = new CircleGeometry(100, 100)
    if (!color) color = !isNewScan ? divideColor : divideColor2
    const circlematerial = new MeshBasicMaterial({
      color,
      transparent: true,
      opacity: 1
    })
    const scanstation = new Mesh(geometry, circlematerial)
    scanstation.position.set(...point)
    scanstation.originalColor = color
    scanstation.selected = false
    scanstation.bimId = id
    return scanstation
  })

  const createSeparator = useMemoizedFn(({line, id, asDoor, color}) => {
    const startP = [line[0], line[1], 110]
    const endP = [line[2], line[3], 110]
    // if (!color) color = new Color(getHexColor(-1023410168))
    if (!color && !asDoor) color = new Color('#ffff00')
    else color = new Color('#00ffff')
    const pointArray = []
    let linematerial
    const lineMats = lineMatsRef.current
    const l = lineMats.dash[color.getHexString()]
    if (l) {
      linematerial = l.clone()
    } else {
      linematerial = new LineDashedMaterial({
        color,
        linewidth: 1,
        dashSize: 100,
        gapSize: 100,
        opacity: 0.8,
        transparent: true
      })
      lineMats.dash[color.getHexString()] = linematerial
    }
    pointArray.push(new Vector3(...startP))
    pointArray.push(new Vector3(...endP))
    const geometry = new BufferGeometry().setFromPoints(pointArray)
    const a_line = new Line(geometry, linematerial)
    a_line.computeLineDistances()
    a_line.originalColor = color
    a_line.selected = false
    a_line.bimId = id
    return a_line
  })

  const createSpatiallivingroom = useMemoizedFn(({vertices, id, color}) => {
    const lines = []
    for (let i = 0; i < vertices.length - 1; i++) {
      const line = [vertices[i][0], vertices[i][1], vertices[i + 1][0], vertices[i + 1][1]]
      console.log(line)
      const startP = [line[0], line[1], 10]
      const endP = [line[2], line[3], 10]
      // if (!color) color = new Color(getHexColor(-1023410168))
      color = new Color('#ff0000')
      const pointArray = []
      let linematerial
      const lineMats = lineMatsRef.current
      const l = lineMats.dash[color.getHexString()]
      if (l) {
        linematerial = l.clone()
      } else {
        linematerial = new LineDashedMaterial({
          color,
          linewidth: 1,
          dashSize: 200,
          gapSize: 200
          // opacity: 0.8,
          // transparent: true
        })
        lineMats.dash[color.getHexString()] = linematerial
      }
      pointArray.push(new Vector3(...startP))
      pointArray.push(new Vector3(...endP))
      const geometry = new BufferGeometry().setFromPoints(pointArray)
      const a_line = new Line(geometry, linematerial)
      a_line.computeLineDistances()
      a_line.originalColor = color
      a_line.selected = false
      a_line.bimId = id
      lines.push(a_line)
    }
    return lines
  })

  const createLine = useMemoizedFn(({ start, end, material, id, layer, color, linewidth }) => {
    const { layerBeamId, layerColumnId, layerMainWallId } = dataForRender
    // if (!data.docs[start]) return 0
    const startP = data.docs[start].location
    const endP = data.docs[end].location
    if (!color) color = new Color(getHexColor(data.docs[material].color))
    const pointArray = []
    let linematerial
    const lineMats = lineMatsRef.current
    if (stage.includes('concrete') && layer !== layerBeamId && layer !== layerColumnId && layer !== layerMainWallId) {
      const l = lineMats.dash[color.getHexString()]
      if (l) {
        linematerial = l.clone()
      } else {
        linematerial = new LineDashedMaterial({
          color,
          linewidth: linewidth ? linewidth : 1,
          dashSize: 10,
          gapSize: 10,
          opacity: 0.1,
          transparent: true
        })
        lineMats.dash[color.getHexString()] = linematerial
      }
    } else {
      const l = lineMats.notDash[color.getHexString()]
      if (l) {
        linematerial = l.clone()
      } else {
        linematerial = new LineBasicMaterial({ color, linewidth: linewidth ? linewidth : 1 })
        lineMats.notDash[color.getHexString()] = linematerial
      }
    }
    pointArray.push(new Vector3(...startP))
    pointArray.push(new Vector3(...endP))
    const geometry = new BufferGeometry().setFromPoints(pointArray)
    const line = new Line(geometry, linematerial)
    line.computeLineDistances()
    line.originalColor = color
    line.selected = false
    line.bimId = id
    line.endPId = end
    line.startPId = start
    return line
  })

  const createDoor = useMemoizedFn(({ lines, id, entry, isNewScan }) => {
    const [start, end] = lines[0]
    // return createLine({ start, end, id, color: entryColor })
    return createLine({ start, end, id, color: entry ? entryColor : (!isNewScan ? divideColor : divideColor2), linewidth: 2 })
  })

  const createWindow = useMemoizedFn(({ lines, id, isNewScan }) => {
    const [start, end] = lines[0]
    return createLine({ start, end, id, color: !isNewScan ? divideColor : divideColor2 })
  })

  const createRoom = useMemoizedFn(
    ({ points, id, isNewScan, color, isRicent, houseId, roomId, room_name, room_num, apartment }) => {
      const narrowCoord = coord => Math.floor(coord)
      points = points.map(i => [narrowCoord(i[0]), narrowCoord(i[1])])
      const allX = []
      const allY = []
      points.forEach(i => {
        allX.push(i[0])
        allY.push(i[1])
      })
      const rectShape = new Shape()
      rectShape.moveTo(...points[0])
      if (isRicent) {
        for (let i = 0; i < points.length; i++) rectShape.lineTo(...points[i])
      } else {
        for (let i = 1; i < points.length; i++) rectShape.lineTo(...points[i])
      }
      rectShape.lineTo(...points[0])
      const geometry = new ShapeGeometry(rectShape)
      if (!color) {
        if (apartment) {
          color = apartmentColorRef.current[apartment]
        } else {
          color = !isNewScan ? divideColor : divideColor2
        }
      }
      const material = new MeshBasicMaterial({
        color,
        transparent: true,
        opacity: 0.3,
        side: DoubleSide
      })
      const solid = new Mesh(geometry, material)
      solid.originalColor = color
      solid.selected = false
      solid.bimId = id
      if (roomId) {
        solid.bimId = roomId
        solid.houseId = houseId
        solid.roomId = roomId
        solid.isRicentDrawingRoom = true
        solid.room_name = room_name
        solid.room_num = room_num
      }
      return solid
    }
  )

  const getTrueCoordinate = useMemoizedFn(({ w, maxX, minX, maxY, minY, point, ricentDrawing }) => {
    const [x, y] = point
    point = [minX + w * (x / 256) - 0.5 * (w - maxX + minX), minY + (w * (256 + y)) / 256 - 0.5 * (w - maxY + minY)]
    if (ricentDrawing.w) {
      if (ricentDrawing.w > ricentDrawing.h) {
        const d = 0.5 * w - (w * (0.5 * ricentDrawing.h)) / ricentDrawing.w
        point[1] = point[1] - d
      } else {
        const d = 0.5 * w - (w * (0.5 * ricentDrawing.w)) / ricentDrawing.h
        point[0] = point[0] + d
      }
    }
    return point
  })

  const createRicentMeshes = useMemoizedFn(({ maxX, minX, maxY, minY, id, ricentDrawing }) => {
    if (isEmpty(ricentDrawing)) return []
    const w = Math.max(maxX - minX, maxY - minY)
    let geometry
    if (ricentDrawing.w) {
      if (ricentDrawing.w > ricentDrawing.h) {
        geometry = new BoxBufferGeometry(w, (w * ricentDrawing.h) / ricentDrawing.w, 0)
      } else {
        geometry = new BoxBufferGeometry((w * ricentDrawing.w) / ricentDrawing.h, w, 0)
      }
    } else {
      geometry = new BoxBufferGeometry(w, w, 0)
    }
    const material = new MeshBasicMaterial({
      map: stage.includes('concrete') ? ricentDrawing.concrete : ricentDrawing.masonry
    })
    const solid = new Mesh(geometry, material)
    solid.position.set(minX + 0.5 * (maxX - minX), minY + 0.5 * (maxY - minY), 0)
    solid.selected = false
    solid.bimId = id
    solid.originalColor = solid.material.color
    const rooms = []
    const names = []
    const scanstations = []
    if (!stage.includes('concrete')) {
      ricentDrawing.layout.forEach(i => {
        const { name, polygon, room_num, position, houseId, id: roomId } = i
        const color = new Color(randomColor())
        polygon.geometry.coordinates.forEach(o => {
          let points
          if (o && o.length > 2) {
            points = o.map(point => getTrueCoordinate({ w, maxX, minX, maxY, minY, point, ricentDrawing }))
          } else if (o[0] && o[0].length > 0) {
            if (getTypeOfVariable(o[0][0]) === 'Array') {
              points = o[0].map(point => getTrueCoordinate({ w, maxX, minX, maxY, minY, point, ricentDrawing }))
            } else {
              points = [getTrueCoordinate({ w, maxX, minX, maxY, minY, point: o, ricentDrawing })]
            }
          }
          if (points.length > 2) {
            rooms.push(createRoom({ points, id, isRicent: true, color, houseId, roomId, room_name: name, room_num }))
          }
        })
        if (font) {
          names.push(
            createText({
              content: `${name}--[${JSON.stringify(room_num)}]`,
              point: getTrueCoordinate({ w, maxX, minX, maxY, minY, point: position, ricentDrawing }),
              id,
              color: new Color('#5584FF'),
              houseId,
              roomId
            })
          )
        }
      })
    }
    ricentDrawing.point.forEach(i => {
      const { extend, geometry } = i
      const point = geometry.split(',').map(c => parseFloat(c))
      if (!stage.includes('concrete')) {
        if (extend === 'masonry,plaster') {
          scanstations.push(
            createScanstation({
              point: getTrueCoordinate({ w, maxX, minX, maxY, minY, point, ricentDrawing }),
              id
            })
          )
        }
      } else {
        if (extend === 'concrete') {
          scanstations.push(
            createScanstation({
              point: getTrueCoordinate({ w, maxX, minX, maxY, minY, point, ricentDrawing }),
              id
            })
          )
        }
      }
    })
    return [solid, ...rooms, ...scanstations, ...names]
  })

  const createBoundingbox = useMemoizedFn(({ maxX, minX, maxY, minY, id }) => {
    const meshes = createRicentMeshes({
      maxX,
      minX,
      maxY,
      minY,
      id,
      ricentDrawing,
      visible: ricentDrawingVisible
    })
    const meshesBackup = createRicentMeshes({
      maxX,
      minX,
      maxY,
      minY,
      id,
      ricentDrawing: ricentDrawingBackup,
      visible: ricentDrawingBackupVisible
    })
    meshes.forEach(i => {
      if (i) i.backup = false
    })
    meshesBackup.forEach(i => {
      if (i) i.backup = true
    })
    return [...meshes, ...meshesBackup]
  })

  const getLocationCoord = useMemoizedFn(id => {
    const location = data.docs[id].location
    const narrowCoord = coord => Math.floor(coord / zoom)
    return [narrowCoord(location[0]), narrowCoord(location[1])]
  })

  const createText = useMemoizedFn(({ content, insert, point, id, color, houseId, roomId }) => {
    if (insert) point = getLocationCoord(insert)
    const [x, y] = point
    let geometry
    const size = color ? '900' : '500'
    const textGeos = textGeosRef.current
    if (textGeos[content] && textGeos[content][size]) {
      geometry = textGeos[content][size]
    } else {
      const shapes = font.generateShapes(content, parseInt(size))
      geometry = new ShapeGeometry(shapes)
      geometry.computeBoundingBox()
      const xMid = -0.5 * (geometry.boundingBox.max.x - geometry.boundingBox.min.x)
      geometry.translate(xMid, 0, 0)
      if (textGeos[content]) {
        textGeos[content][size] = geometry
      } else {
        textGeos[content] = {}
        textGeos[content][size] = geometry
      }
    }
    if (!color) color = new Color('#ffffff')
    let material
    if (stage.includes('concrete')) {
      material = new MeshBasicMaterial({ color, transparent: true, opacity: 0.1 })
    } else {
      material = new MeshBasicMaterial({ color })
    }
    const text = new Mesh(geometry, material)
    text.position.set(x, y, 0)
    text.originalColor = color
    text.selected = false
    text.bimId = id
    text.houseId = houseId
    text.roomId = roomId
    return text
  })

  const createArc = useMemoizedFn(({ center, end_point: endP, start_point: startP, radius, material, layer, id }) => {
    const { layerBeamId, layerColumnId, layerMainWallId } = dataForRender
    const color = new Color(getHexColor(data.docs[material].color))
    let arcmaterial
    if (stage.includes('concrete') && layer !== layerBeamId && layer !== layerColumnId && layer !== layerMainWallId) {
      arcmaterial = new LineDashedMaterial({
        color,
        linewidth: 1,
        dashSize: 10,
        gapSize: 10,
        opacity: 0.1,
        transparent: true
      })
    } else {
      arcmaterial = new LineBasicMaterial({ color, linewidth: 1 })
    }
    const { location } = data.docs[center]
    // start and end point
    const start_location = data.docs[startP].location
    const end_location = data.docs[endP].location

    // 默认逆时针开始画
    const startradius = new Vector2(start_location[0] - location[0], start_location[1] - location[1]).angle()
    const endradius = new Vector2(end_location[0] - location[0], end_location[1] - location[1]).angle()
    const aClockwise = false
    const curve = new EllipseCurve(location[0], location[1], radius, radius, startradius, endradius, aClockwise)
    const curve_points = curve.getPoints(50)
    const geometry = new BufferGeometry().setFromPoints(curve_points)
    const ellipse = new Line(geometry, arcmaterial)
    ellipse.originalColor = color
    ellipse.selected = false
    ellipse.bimId = id
    return ellipse
  })

  const createEllipse = useMemoizedFn(({ center, axesLength, angle, startAngle, endAngle, material, layer, id }) => {
    const { layerBeamId, layerColumnId, layerMainWallId } = dataForRender
    const color = new Color(getHexColor(data.docs[material].color))
    let ellipsematerial
    if (stage.includes('concrete') && layer !== layerBeamId && layer !== layerColumnId && layer !== layerMainWallId) {
      ellipsematerial = new LineDashedMaterial({
        color,
        linewidth: 1,
        dashSize: 10,
        gapSize: 10,
        opacity: 0.1,
        transparent: true
      })
    } else {
      ellipsematerial = new LineBasicMaterial({ color, linewidth: 1 })
    }
    const { location } = data.docs[center]
    const aClockwise = false
    const curve = new EllipseCurve(0, 0, axesLength[0], axesLength[1], startAngle, endAngle, aClockwise)
    const curve_points = curve.getPoints(50)
    const geometry = new BufferGeometry().setFromPoints(curve_points)
    geometry.rotateZ(angle)
    geometry.translate(...location)
    const ellipse = new Line(geometry, ellipsematerial)
    ellipse.originalColor = color
    ellipse.selected = false
    ellipse.bimId = id
    return ellipse
  })

  const fitAndCenter = useMemoizedFn(() => {
    onWindowResize()
    const group = groupRef.current
    const box3 = new Box3()
    box3.expandByObject(group)
    const v3 = new Vector3()
    box3.getSize(v3)
    function num() {
      let max
      if (v3.x > v3.y) {
        max = v3.x
      } else {
        max = v3.y
      }
      if (max < v3.z) {
        max = v3.z
      }
      return max
    }
    const S = (widthRef.current * 1.8) / num()
    if (!SRef.current) {
      group.scale.set(S, S, S)
      SRef.current = S
    }
    const newBox3 = new Box3()
    newBox3.expandByObject(group)
    const center = new Vector3()
    newBox3.getCenter(center)
    cameraRef.current.position.x = center.x
    cameraRef.current.position.y = center.y
    cameraRef.current.zoom = 1
    cameraRef.current.position.z = 1000
    cameraRef.current.updateProjectionMatrix()
    groupPositionRef.current = {
      x: group.position.x,
      y: group.position.y,
      z: group.position.z
    }
    controlsRef.current.target = new Vector3(center.x, center.y, 0)
    controlsRef.current.update()
    if (!centerRef.current) {
      centerRef.current = center
    }
  })

  const getStdVector = useMemoizedFn(event => {
    const pointerX = event.clientX //鼠标单击坐标X
    const pointerY = event.clientY //鼠标单击坐标Y
    const rect = graphRef.current.getBoundingClientRect()
    const x = ((pointerX - rect.left) / rect.width) * 2 - 1
    const y = -((pointerY - rect.top) / rect.height) * 2 + 1
    return new Vector3(x, y, 0)
  })

  const getTrueVector = useMemoizedFn(event => {
    const stdVector = getStdVector(event)
    let { x, y } = stdVector.unproject(cameraRef.current)
    x = x / SRef.current
    y = y / SRef.current
    return { x, y }
  })

  const scanning = cell => {
    cell.material.color = scanningColor
    cell.selected = true
  }

  const selected = cell => {
    cell.position.z = 100
    cell.material.color = selectedColor
    cell.selected = true
  }

  const unSelected = cell => {
    cell.position.z = 0
    cell.material.color = cell.originalColor
    cell.selected = false
  }

  const createRaycaster = useMemoizedFn(() => {
    raycasterRef.current = new Raycaster()
    raycasterRef.current.params = {
      Mesh: { threshold: 60 },
      Line: { threshold: 60 }
    }
    raycasterRef.current.far = 100000
    raycasterRef.current.near = 0
  })

  const handleButtonClick = useMemoizedFn(({ e, bool }) => {
    const key = e.target.innerText
    let mergeList
    switch (key) {
      case '刷新':
        // refresh()
        break
      case '居中':
        onWindowResize()
        fitAndCenter()
        break
      case '户型标准':
        updateApartmentName()
        break
      case '切换房间显示':
        setRoomVisible(bool)
        break
      case '切换站点显示':
        setScanstationVisible(bool)
        break
      case '切换门显示':
        setDoorVisible(bool)
        break
      case '切换窗显示':
        setWindowVisible(bool)
        break
      case '自动规划':
        autoplan({ cb: bool => setButtonLoading({ 自动规划: bool }) })
        break
      case '手动规划':
        setPlanningScanstationState(bool)
        break
      case '合并房间':
        mergeList = [
          selectedIds.filter(id => bim.docs[id] && bim.docs[id].definition === 'room').map(id => bim.docs[id].mergeNum)
        ]
        autoplan({ cb: bool => setButtonLoading({ 合并房间: bool }), mergeList })
        break
      case '清空图纸':
        removeDb()
        break
      case '自动规划(新)':
        autoplan({ cb: bool => setButtonLoading({ '自动规划(新)': bool }), type: 'new' })
        break
      case '手动规划(新)':
        setNewPlanningScanstationState(bool)
        break
      case '划分户型(新)':
        mergeList = [
          selectedIds.filter(id => bim.docs[id] && bim.docs[id].definition === 'room').map(id => bim.docs[id].mergeNum)
        ]
        autoplan({ cb: bool => setButtonLoading({ '划分户型(新)': bool }), mergeList, type: 'new' })
        break
      case '站点检查':
        scanstationCheck()
        break
      default:
    }
  })

  const shouldSelected = useMemoizedFn(item => {
    if (!item) return false
    if (item.bimId) {
      if (getTypeOfVariable(item.backup) === 'Boolean' && !item.isRicentDrawingRoom) return false
      if (!data.docs[item.bimId]) return true
      const layer = data.docs[item.bimId].layer
      if (!layer) return true
      const { layerBeamId, layerColumnId, layerMainWallId } = dataForRender
      if (stage.includes('concrete') && layer !== layerBeamId && layer !== layerColumnId && layer !== layerMainWallId) {
        return false
      }
    }
    return true
  })

  const shouldSelectedObj = useMemoizedFn(obj => {
    const newObj = {}
    for (const bimId in obj) {
      if (shouldSelected(obj[bimId])) {
        newObj[bimId] = obj[bimId]
      }
    }
    return newObj
  })

  const setOverlayOffset = useMemoizedFn(arg => rightClickMenuRef.current.setOverlayOffset(arg))

  const setOverlayVisible = useMemoizedFn(arg => rightClickMenuRef.current.setOverlayVisible(arg))

  const pointerdown = useMemoizedFn(event => {
    buttonRef.current = event.button
    if (buttonRef.current === 0) {
      if (selectionBox.collection.length > 0) {
        const newSelectedCells = { ...selectedCells }
        for (const item of selectionBox.collection) {
          newSelectedCells[item.bimId] = undefined
        }
        if (!event.ctrlKey) setSelectedIds(shouldSelectedObj(newSelectedCells))
        selectionBox.collection = []
      }
      const { x, y } = getStdVector(event)
      selectionBox.startPoint.set(x, y, 1)
      selectionBoxStartPRef.current = [x, y]
    } else if (buttonRef.current === 1) {
      document.body.style.cursor = 'move'
    }
  })

  const getIntersects = useMemoizedFn(event => {
    const stdVector = getStdVector(event)
    raycasterRef.current.setFromCamera(new Vector2(stdVector.x, stdVector.y), cameraRef.current)
    const intersects = raycasterRef.current.intersectObjects(sceneRef.current.children, true)
    return intersects.filter(i => i.object.material.visible)
  })

  const pointermove = useMemoizedFn(event => {
    if (centerRef.current && SRef.current) {
      const { x, y } = getTrueVector(event)
      setCoord({ x, y })
    }
    if (buttonRef.current === 0) {
      if (!ricentDrawingVisible && !ricentDrawingBackupVisible) {
        if (helper.isDown) {
          pointermoveRef.current = true
          for (let i = 0; i < selectionBox.collection.length; i++) {
            const item = selectionBox.collection[i]
            item.material.color = item.originalColor
            item.selected = false
          }
          const { x, y } = getStdVector(event)
          const [startPX, startPY] = selectionBoxStartPRef.current
          if (startPX !== x || startPY !== y) {
            selectionBox.endPoint.set(x, y, 1)
            const allSelected = selectionBox.select()
            for (let i = 0; i < allSelected.length; i++) {
              if (shouldSelected(allSelected[i])) {
                allSelected[i].material.color = selectedColor
                allSelected[i].selected = true
              }
            }
          }
        }
      }
    } else if (buttonRef.current === 1) {
      document.body.style.cursor = 'move'
    } else {
      if (!helper.isDown) {
        const intersects = getIntersects(event)
        if (intersects.length > 0) {
          let item
          for (let i = 0; i < intersects.length; i++) {
            const cell = intersects[i].object
            if (cell.material.visible) {
              if (getTypeOfVariable(cell.backup) === 'Boolean') {
                if (cell.isRicentDrawingRoom) {
                  item = cell
                  break
                } else {
                  continue
                }
              } else {
                item = cell
                break
              }
            } else {
              continue
            }
          }
          if (item) {
            document.body.style.cursor = 'pointer'
            if (stage.includes('concrete')) {
              if (data.docs[item.bimId]) {
                const arr = [dataForRender.layerBeamId, dataForRender.layerColumnId, dataForRender.layerMainWallId]
                if (!arr.includes(data.docs[item.bimId].layer)) {
                  document.body.style.cursor = 'auto'
                }
              }
            }
          } else {
            document.body.style.cursor = 'auto'
          }
        } else {
          document.body.style.cursor = 'auto'
        }
      }
    }
    setOverlayOffset([event.clientX, event.clientY])
    setOverlayVisible(false)
  })

  const pointerup = useMemoizedFn(event => {
    if (buttonRef.current === 0) {
      if (planningScanstationState && event.button === 0) {
        if (centerRef.current && SRef.current) {
          const { x, y } = getTrueVector(event)
          const p = createScanstation({ point: [x, y], color: '#1890ff' })
          groupRef.current.add(p)
          if (!event.ctrlKey) {
            manualplan({
              points: [[x, y]],
              cb: arg => {
                setButtonLoading(arg)
                if (!arg) groupRef.current.remove(p)
              }
            })
          } else {
            pointsToPlanRef.current.push(p)
          }
        }
      } else if (newPlanningScanstationState && event.button === 0) {
        if (centerRef.current && SRef.current) {
          const { x, y } = getTrueVector(event)
          const p = createScanstation({ point: [x, y], color: '#1890ff' })
          groupRef.current.add(p)
          if (!event.ctrlKey) {
            manualplan({
              point: [[x, y]],
              cb: arg => {
                setButtonLoading(arg)
                if (!arg) groupRef.current.remove(p)
              },
              type: 'new'
            })
          } else {
            pointsToPlanRef.current.push(p)
          }
        }
      } else {
        if (pointermoveRef.current) {
          if (event.button === 0) {
            const { x, y } = getStdVector(event)
            const [startPX, startPY] = selectionBoxStartPRef.current
            if (startPX !== x || startPY !== y) {
              selectionBox.endPoint.set(x, y, 1)
              const allSelected = selectionBox.select()
              if (allSelected.length > 0) {
                const newSelectedCells = { ...selectedCells }
                for (let i = 0; i < allSelected.length; i++) {
                  const item = allSelected[i]
                  newSelectedCells[item.bimId] = item
                }
                setSelectedIds(shouldSelectedObj(newSelectedCells))
              }
            }
          }
          pointermoveRef.current = false
        } else {
          if (event.button === 0) {
            const intersects = getIntersects(event)
            if (intersects.length > 0) {
              let item
              for (let i = 0; i < intersects.length; i++) {
                const cell = intersects[i].object
                if (cell.material.visible) {
                  if (getTypeOfVariable(cell.backup) === 'Boolean') {
                    if (cell.isRicentDrawingRoom) {
                      item = cell
                      break
                    } else {
                      continue
                    }
                  } else {
                    item = cell
                    break
                  }
                } else {
                  continue
                }
              }
              if (item) {
                if (ricentDrawingVisible || ricentDrawingBackupVisible) {
                  if (window.ricentDrawingRoom && !item.backup) {
                    const { room_name, room_num } = window.ricentDrawingRoom
                    const group = groupRef.current
                    group.children.forEach(i => {
                      if (!i.isRicentDrawingRoom && i.roomId === item.roomId) {
                        i.geometry = new TextGeometry(`${room_name}--[${JSON.stringify(room_num)}]`, {
                          font: font,
                          size: 900
                        })
                      }
                      if (i.isRicentDrawingRoom && !i.backup && i.bimId === item.bimId) {
                        i.room_name = room_name
                        i.room_num = room_num
                      }
                    })
                    window.ricentDrawingRoom = null
                  } else if (item.backup) {
                    window.ricentDrawingRoom = item
                  }
                  setSelectedIds({})
                  setSelectedRicentCells({ [item.bimId]: item })
                } else if (window.ricentDrawingRoom) {
                  if (item && item.bimId && bim.docs[item.bimId] && bim.docs[item.bimId].definition === 'room') {
                    const apartmentId = bim.docs[item.bimId].apartment
                    const roomIds = bim.cols.room.filter(i => i.apartment === apartmentId).map(i => i.id)
                    const selectCells = {}
                    const group = groupRef.current
                    roomIds.forEach(id => {
                      selectCells[id] = group.children.filter(o => o.bimId === id)
                    })
                    const { room_name, room_num } = window.ricentDrawingRoom
                    setSelectedIds(selectCells)
                    updateBIM({ id: apartmentId, name: room_name, num: room_num })
                  }
                  window.ricentDrawingRoom = null
                } else {
                  window.ricentDrawingRoom = null
                  if (event.ctrlKey) {
                    const newSelectedCells = { ...selectedCells }
                    if (newSelectedCells[item.bimId]) {
                      newSelectedCells[item.bimId] = undefined
                    } else {
                      newSelectedCells[item.bimId] = item
                    }
                    setSelectedIds(shouldSelectedObj(newSelectedCells))
                  } else {
                    if (shouldSelected(item)) {
                      setSelectedIds({ [item.bimId]: item })
                    } else {
                      setSelectedIds({})
                    }
                  }
                }
              } else {
                window.ricentDrawingRoom = null
                setSelectedIds({})
                if (!isEmpty(selectedRicentCells)) setSelectedRicentCells({})
              }
            } else {
              window.ricentDrawingRoom = null
              setSelectedIds({})
              if (!isEmpty(selectedRicentCells)) setSelectedRicentCells({})
            }
          }
        }
      }
    } else if (event.button === 1) {
      document.body.style.cursor = 'auto'
    } else if (event.button === 2) {
      setOverlayVisible()
    }
    buttonRef.current = -1
  })

  const keyup = useMemoizedFn(event => {
    if (event.keyCode === 46) {
      const selectScanstationIds = selectedIds.filter(id => bim.docs[id] && bim.docs[id].definition === 'scanstation')
      deleteBIMs({ ids: selectScanstationIds, type: 'scanstation' })
    }
    if (event.keyCode === 17 && pointsToPlanRef.current.length > 0) {
      if (planningScanstationState) {
        manualplan({
          points: pointsToPlanRef.current.map(p => {
            const { position } = p
            return [position.x, position.y]
          }),
          cb: arg => {
            setButtonLoading(arg)
            if (!arg) {
              pointsToPlanRef.current.forEach(p => groupRef.current.remove(p))
              pointsToPlanRef.current = []
            }
          }
        })
      } else if (newPlanningScanstationState) {
        manualplan({
          points: pointsToPlanRef.current.map(p => {
            const { position } = p
            return [position.x, position.y]
          }),
          cb: arg => {
            setButtonLoading(arg)
            if (!arg) {
              pointsToPlanRef.current.forEach(p => groupRef.current.remove(p))
              pointsToPlanRef.current = []
            }
          },
          type: 'new'
        })
      } else {
        pointsToPlanRef.current.forEach(p => groupRef.current.remove(p))
        pointsToPlanRef.current = []
      }
    }
  })

  const updateRoomsNumAndLayoutData = useMemoizedFn(async () => {
    if (!isEmpty(ricentDrawing) && ricentDrawing.layout.length > 0) {
      const stageId = ricentDrawing.layout[0].regionId
      const data = {}
      const group = groupRef.current
      group.children.forEach(i => {
        if (i.isRicentDrawingRoom && !i.backup) {
          data[i.houseId] = {
            name: i.room_name,
            room_num: i.room_num
          }
        }
      })
      await graphDispatchers.updateRoomsNumAndLayoutData({ stageId, data })
    }
  })

  // eslint-disable-next-line no-use-before-define
  useEventListener('pointerdown', pointerdown, { target: graphRef.current })
  // eslint-disable-next-line no-use-before-define
  useEventListener('pointermove', pointermove, { target: graphRef.current })
  // eslint-disable-next-line no-use-before-define
  useEventListener('pointerup', pointerup, { target: graphRef.current })
  // eslint-disable-next-line no-use-before-define
  useEventListener('keyup', keyup)
  // eslint-disable-next-line no-use-before-define
  useEventListener('resize', onWindowResize)

  useImperativeHandle(
    ref,
    () => ({
      getSelectionCellIDs: () => Object.keys(selectedCells),
      getSelectionCells: () => {
        const selectionCells = []
        for (const id in selectedCells) selectionCells.push({ ...selectedCells[id], id })
        return selectionCells
      },
      handleButtonClick: handleButtonClick,
      setSwitchButtonState: key => {
        switch (key) {
          case 'ugm图纸':
            setRicentDrawingVisible(false)
            setRicentDrawingBackupVisible(false)
            break
          case '备份图纸':
            setRicentDrawingVisible(false)
            setRicentDrawingBackupVisible(true)
            break
          case '瑞信图纸':
            setRicentDrawingVisible(true)
            setRicentDrawingBackupVisible(false)
            break
          default:
        }
      },
      setRotate: bool => setRotateZ(bool ? rotateZ + 0.5 * Math.PI : rotateZ - 0.5 * Math.PI),
      updateRoomsNumAndLayoutData: updateRoomsNumAndLayoutData
    }),
    [
      selectedCells,
      handleButtonClick,
      setRicentDrawingVisible,
      setRicentDrawingBackupVisible,
      setRotateZ,
      rotateZ,
      updateRoomsNumAndLayoutData
    ]
  )

  return (
    <>
      <div ref={graphRef} className={styles.graph} />
      <RightClickMenu ref={rightClickMenuRef} back={back} forward={forward} />
      <Loading visible={loadingVisible || loadingVisible} shape="fusion-reactor"></Loading>
    </>
  )
}

export default forwardRef(Graph)
